package com.he.service.impl;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.lang.Assert;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.he.constant.CommonConstant;
import com.he.constant.RedisConstant;
import com.he.domain.Result;
import com.he.domain.dto.*;
import com.he.domain.vo.*;
import com.he.entity.BlogFile;
import com.he.entity.LoginUser;
import com.he.entity.User;
import com.he.entity.UserRole;
import com.he.enums.FilePathEnum;
import com.he.mapper.*;
import com.he.service.UserService;
import com.he.strategy.context.UploadStrategyContext;
import com.he.utils.PageUtils;
import com.he.utils.RedisCache;
import com.he.utils.SecurityUtils;
import jakarta.annotation.Resource;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;

import java.util.*;
import java.util.stream.Collectors;

/**
 * @author He
 * @version 1.0
 * @Date 2023/9/7 16:47
 * @Desc 用户业务实现类
 */
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {


    @Resource
    private RedisCache redisCache;

    @Resource
    private PasswordEncoder passwordEncoder;

    @Resource
    private UserMapper userMapper;

    @Resource
    private MenuMapper menuMapper;

    @Resource
    private BlogFileMapper blogFileMapper;

    @Resource
    private RoleMapper roleMapper;

    @Resource
    private UserRoleMapper userRoleMapper;

    /**
     * 上传策略上下文
     */
    @Resource
    private UploadStrategyContext uploadStrategyContext;

    @Override
    public Result<?> register(RegisterDTO register) {
        return null;
    }

    @Override
    public UserInfoVO getUserInfo() {
        Integer userId = SecurityUtils.getLoginUser().getUser().getId();
        User user = getById(userId);
        //TODO 点赞集合
        return UserInfoVO.builder()
                .id(user.getId())
                .username(user.getUsername())
                .avatar(user.getAvatar())
                .nickname(user.getNickname())
                .email(user.getEmail())
                .webSite(user.getWebSite())
                .intro(user.getIntro())
                .build();
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public void updateUserInfo(UserInfoDTO userInfoDTO) {
        LoginUser loginUser = SecurityUtils.getLoginUser();
        User user = loginUser.getUser();
        User newUser = User.builder()
                .id(user.getId())
                .nickname(userInfoDTO.getNickname())
                .webSite(userInfoDTO.getWebSite())
                .intro(userInfoDTO.getIntro())
                .build();
        updateById(newUser);
    }

    @Override
    public void updatePassword(UserPasswordDTO userPasswordDTO) {
        verifyCode(userPasswordDTO.getUsername(), userPasswordDTO.getCode());
        update(new User(), new LambdaUpdateWrapper<User>()
                .set(User::getPassword, passwordEncoder.encode(userPasswordDTO.getPassword()))
                .eq(User::getUsername, userPasswordDTO.getUsername()));
        if(Objects.nonNull(SecurityUtils.getLoginUser())) {
            redisCache.deleteObject(RedisConstant.LOGIN_USER_KEY_PREFIX + SecurityUtils.getLoginUser().getUser().getId());
        }
    }



    /**
     * 更新用户头像
     *
     * @param file
     * @return 头像地址
     */
    @Transactional(rollbackFor = Exception.class)
    @Override
    public String updateAvatar(MultipartFile file) {
        String avatar = uploadStrategyContext.executeUploadStrategy(file, FilePathEnum.AVATAR.getPath());
        String fileNameWithoutExtension = FilenameUtils.getBaseName(avatar);
        // 后缀
        String extension = FilenameUtils.getExtension(avatar);
        BlogFile blogFile = BlogFile.builder()
                .fileUrl(avatar)
                .filePath(FilePathEnum.AVATAR.getFilePath())
                .fileName(fileNameWithoutExtension)
                .extendName(extension)
                .fileSize((int) file.getSize())
                .isDir(CommonConstant.FALSE)
                .build();
        blogFileMapper.insert(blogFile);
        User user = User.builder()
                .id(SecurityUtils.getLoginUser().getUser().getId())
                .avatar(avatar)
                .build();
        updateById(user);
        return avatar;
    }


    /**
     * 获取后台用户信息
     *
     * @return {@link UserBackInfoVO}
     */
    @Override
    public UserBackInfoVO getUserBackInfo() {
        Integer userId = SecurityUtils.getUserId();
        // 查询用户信息
        User user = userMapper.selectOne(new LambdaQueryWrapper<User>()
                .select(User::getAvatar).eq(User::getId, userId));
        // 查询用户角色
        List<String> roleIdList = SecurityUtils.getLoginUser().getRoles();
        // 用户权限列表
        List<String> permissionList = SecurityUtils.getLoginUser().getPermissions().stream()
                .filter(string -> !string.isEmpty())
                .distinct()
                .collect(Collectors.toList());
        return UserBackInfoVO.builder()
                .id(userId)
                .avatar(user.getAvatar())
                .roleList(roleIdList)
                .permissionList(permissionList)
                .build();
    }

    @Override
    public List<RouterVO> getUserMenu() {
        // 查询用户菜单
        List<UserMenuVO> userMenuVOList = menuMapper.selectMenuByUserId(SecurityUtils.getUserId());
        // 递归生成路由,parentId为0
        return recurRoutes(CommonConstant.PARENT_ID, userMenuVOList);
    }

    @Override
    public void updateAdminPassword(PasswordDTO password) {
        Integer userId = SecurityUtils.getUserId();
        User user = userMapper.selectOne(new LambdaQueryWrapper<User>()
                .eq(User::getId, userId));
        Assert.notNull(user, "用户不存在");
        Assert.isTrue(passwordEncoder.matches(password.getOldPassword(), user.getPassword()), "旧密码错误");
        user.setPassword(passwordEncoder.encode(password.getNewPassword()));
        userMapper.updateById(user);
    }

    @Override
    public PageResult<UserBackVO> listUserBackVO(ConditionDTO condition) {
        // 查询后台用户数量
        Long count = userMapper.countUser(condition);
        if (count == 0) {
            return new PageResult<>();
        }
        // 查询后台用户列表
        List<UserBackVO> userBackVOList = userMapper.listUserBackVO(PageUtils.getLimit(), PageUtils.getSize(), condition);
        return new PageResult<>(userBackVOList, count);
    }

    @Override
    public List<UserRoleVO> listUserRoleDTO() {
        // 查询角色列表
        return roleMapper.selectUserRoleList();
    }

    @Override
    public void updateUser(UserRoleDTO user) {
        // 更新用户信息
        User newUser = User.builder()
                .id(user.getId())
                .nickname(user.getNickname())
                .build();
        baseMapper.updateById(newUser);
        // 删除用户角色
        userRoleMapper.delete(new LambdaQueryWrapper<UserRole>().eq(UserRole::getUserId, user.getId()));
        // 重新添加用户角色
        userRoleMapper.insertUserRole(user.getId(), user.getRoleIdList());
        if(user.getId().equals(SecurityUtils.getUserId())) {
            redisCache.deleteObject(RedisConstant.LOGIN_USER_KEY_PREFIX + SecurityUtils.getUserId());
        }
    }

    @Override
    public void updateUserStatus(DisableDTO disable) {
        // 更新用户状态
        User user = User.builder()
                .id(disable.getId())
                .isDisable(disable.getIsDisable())
                .build();
        baseMapper.updateById(user);
        if(disable.getId().equals(SecurityUtils.getUserId())) {
            redisCache.deleteObject(RedisConstant.LOGIN_USER_KEY_PREFIX + SecurityUtils.getUserId());
        }
    }

    @Override
    public PageResult<OnlineVO> listOnlineUser(ConditionDTO condition) {
        // 查询在线用户
        Set<String> keyNames = redisCache.searchKeysWithPrefix(RedisConstant.LOGIN_USER_KEY_PREFIX);
        List<LoginUser> cacheList = new ArrayList<>();
        // 获取在线用户信息
        for (String key : keyNames) {
            cacheList.add((LoginUser) redisCache.getCacheObject(key));
        }
        List<OnlineVO> onlineVOList = cacheList.stream().map(
                loginUser -> {
                    User user = loginUser.getUser();
                    OnlineVO onlineVO = new OnlineVO();
                    BeanUtils.copyProperties(user, onlineVO);
                    onlineVO.setBrowser(loginUser.getBrowser());
                    onlineVO.setOs(loginUser.getOs());
                    onlineVO.setLoginTime(loginUser.getLoginTime());
                    return onlineVO;
                }
        ).filter(onlineVO ->
                {
                    return (StringUtils.isEmpty(condition.getKeyword()) ||
                            StringUtils.containsIgnoreCase(onlineVO.getNickname(), condition.getKeyword()));
                })
                .collect(Collectors.toList());
        int fromIndex = PageUtils.getLimit().intValue();
        int size = PageUtils.getSize().intValue();
        int toIndex = onlineVOList.size() - fromIndex > size ? fromIndex + size : onlineVOList.size();
        List<OnlineVO> userOnlineList = onlineVOList.subList(fromIndex, toIndex);
        return new PageResult<>(userOnlineList, (long) onlineVOList.size());
    }

    @Override
    public void kickOutUser(String userId) {
        redisCache.deleteObject(RedisConstant.LOGIN_USER_KEY_PREFIX + userId);
    }

    @Override
    public void deleteUser(Integer id) {
        // 删除用户
        baseMapper.deleteById(id);
        // 删除用户角色
        userRoleMapper.delete(new LambdaQueryWrapper<UserRole>().eq(UserRole::getUserId, id));
    }

    /**
     * 递归生成路由列表
     *
     * @param parentId 父级ID
     * @param menuList 菜单列表
     * @return 路由列表
     */
    private List<RouterVO> recurRoutes(Integer parentId, List<UserMenuVO> menuList) {
        List<RouterVO> list = new ArrayList<>();
        Optional.ofNullable(menuList).ifPresent(menus -> menus.stream()
                .filter(menu -> menu.getParentId().equals(parentId))
                .forEach(menu -> {
                    RouterVO routeVO = new RouterVO();
                    routeVO.setName(menu.getMenuName());
                    routeVO.setPath(getRouterPath(menu));
                    routeVO.setComponent(getComponent(menu));
                    routeVO.setMeta(MetaVO.builder()
                            .title(menu.getMenuName())
                            .icon(menu.getIcon())
                            .hidden(menu.getIsHidden().equals(CommonConstant.TRUE))
                            .build());
                    if (menu.getMenuType().equals(CommonConstant.TYPE_DIR)) { //M
                        List<RouterVO> children = recurRoutes(menu.getId(), menuList);
                        if (CollectionUtil.isNotEmpty(children)) {
                            routeVO.setAlwaysShow(true);
                            routeVO.setRedirect("noRedirect");
                        }
                        routeVO.setChildren(children);
                    } else if (isMenuFrame(menu)) {
                        routeVO.setMeta(null);
                        List<RouterVO> childrenList = new ArrayList<>();
                        RouterVO children = new RouterVO();
                        children.setName(menu.getMenuName());
                        children.setPath(menu.getPath());
                        children.setComponent(menu.getComponent());
                        children.setMeta(MetaVO.builder()
                                .title(menu.getMenuName())
                                .icon(menu.getIcon())
                                .hidden(menu.getIsHidden().equals(CommonConstant.TRUE))
                                .build());
                        childrenList.add(children);
                        routeVO.setChildren(childrenList);
                    }
                    list.add(routeVO);
                }));
        return list;
    }

    /**
     * 获取路由地址
     *
     * @param menu 菜单信息
     * @return 路由地址
     */
    public String getRouterPath(UserMenuVO menu) {
        String routerPath = menu.getPath();
        // 一级目录
        if (menu.getParentId().equals(CommonConstant.PARENT_ID) && CommonConstant.TYPE_DIR.equals(menu.getMenuType())) {
            routerPath = "/" + menu.getPath();
        }
        // 一级菜单
        else if (isMenuFrame(menu)) {
            routerPath = "/";
        }
        return routerPath;
    }

    /**
     * 获取组件信息
     *
     * @param menu 菜单信息
     * @return 组件信息
     */
    public String getComponent(UserMenuVO menu) {
        String component = CommonConstant.LAYOUT;
        if (StringUtils.isNotEmpty(menu.getComponent()) && !isMenuFrame(menu)) {
            component = menu.getComponent();
        } else if (StringUtils.isEmpty(menu.getComponent()) && isParentView(menu)) {
            component = CommonConstant.PARENT_VIEW;
        }
        return component;
    }

    /**
     * 是否为菜单内部跳转
     *
     * @param menu 菜单信息
     * @return 结果
     */
    public boolean isMenuFrame(UserMenuVO menu) {
        return menu.getParentId().equals(CommonConstant.PARENT_ID) && CommonConstant.TYPE_MENU.equals(menu.getMenuType());
    }

    /**
     * 是否为parent_view组件
     *
     * @param menu 菜单信息
     * @return 结果
     */
    public boolean isParentView(UserMenuVO menu) {
        return !menu.getParentId().equals(CommonConstant.PARENT_ID) && CommonConstant.TYPE_DIR.equals(menu.getMenuType());
    }

    /**
     * 校验验证码
     *
     * @param username 用户名
     * @param code     验证码
     */
    public void verifyCode(String username, String code) {
        String sysCode = redisCache.getCacheObject(RedisConstant.VERIFY_CODE_KEY + username);
        Assert.notBlank(sysCode, "验证码未发送或已过期！");
        Assert.isTrue(sysCode.equals(code), "验证码错误，请重新输入！");
    }
}
